using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;

using VSTalk.Engine.Core.XmppUtils;
using VSTalk.Model;

using agsXMPP;

namespace VSTalk.Engine.Core.Context
{
    public class ModelContext : IModelContext, IDisposable
    {
        private Account _account;
        private ObservableCollection<Interlocutor> _interlocutors = new ObservableCollection<Interlocutor>();
        private ObservableCollection<Room> _rooms = new ObservableCollection<Room>();

        public Account Account
        {
            get { return _account; }
            set
            {
                if (Equals(_account, value)) return;
                _account = value;
                if (_account != null)
                {
                    BindCollections();
                }
            }
        }

        public ObservableCollection<Client> Clients
        {
            get { return _account.XmppClients; }
        }

        public ObservableCollection<Interlocutor> Interlocutors
        {
            get { return _interlocutors; }
            private set { _interlocutors = value; }
        }

        public ObservableCollection<Room> Rooms
        {
            get { return _rooms; }
            private set { _rooms = value; }
        }

        public Client GetClientById(Guid id)
        {
            return _account.XmppClients.FirstOrDefault(client => client.Id == id);
        }

        public Client GetClientByJid(Jid jid)
        {
            return _account.XmppClients.FirstOrDefault(client => string.Equals(jid.Bare, XmppIdConverter.Jid(client).Bare));
        }

        public Interlocutor GetContactById(Guid id)
        {
            return _account.XmppClients.SelectMany(client => client.Contacts)
                    .FirstOrDefault(client => client.Id == id);
        }

        public Interlocutor GetContactByJid(Guid clientId, Jid jid)
        {
            return Clients.Where(client => client.Id == clientId)
                    .SelectMany(client => client.Contacts)
                    .FirstOrDefault(interlocutor => string.Equals(jid.Bare, XmppIdConverter.Jid(interlocutor).Bare));
        }

        public Room GetRoomById(Guid id)
        {
            return Rooms.FirstOrDefault(room => room.Id == id);
        }

        public Room GetRoomByJid(Guid clientId, Jid jid)
        {
            return Clients.Where(client => client.Id == clientId)
                    .SelectMany(client => client.Rooms)
                    .FirstOrDefault(room => string.Equals(jid.Bare, XmppIdConverter.Jid(room).Bare));

        }

        public Client GetClientByContact(Interlocutor interlocutor)
        {
            return _account.XmppClients.FirstOrDefault(client => client.Contacts.Contains(interlocutor));
        }

        public Client GetClientByRoom(Room room)
        {
            return _account.XmppClients.FirstOrDefault(client => client.Rooms.Contains(room));
        }

        private void BindCollections()
        {
            FillCollections();
            SubscribeCollections();
        }

        private void FillCollections()
        {
            Interlocutors.Clear();
            Rooms.Clear();
            foreach (var client in Clients)
            {
                client.Contacts.CollectionChanged += ContactsOnCollectionChanged;
                foreach (Interlocutor interlocutor in client.Contacts)
                {
                    Interlocutors.Add(interlocutor);
                }

                client.Rooms.CollectionChanged += RoomsOnCollectionChanged;
                foreach (Room room in client.Rooms)
                {
                    Rooms.Add(room);
                }
            }
        }

        private void SubscribeCollections()
        {
            Clients.CollectionChanged += (sender, args) =>
            {
                if (args.NewItems != null)
                {
                    foreach (Client newItem in args.NewItems)
                    {
                        newItem.Rooms.CollectionChanged += RoomsOnCollectionChanged;
                        foreach (Room room in newItem.Rooms)
                        {
                            Rooms.Add(room);
                        }

                        newItem.Contacts.CollectionChanged += ContactsOnCollectionChanged;
                        foreach (Interlocutor contact in newItem.Contacts)
                        {
                            Interlocutors.Add(contact);
                        }
                    }
                }
                if (args.OldItems != null)
                {
                    foreach (Client oldItem in args.OldItems)
                    {
                        oldItem.Rooms.CollectionChanged -= RoomsOnCollectionChanged;
                        foreach (Room room in oldItem.Rooms)
                        {
                            Rooms.Remove(room);
                        }

                        oldItem.Contacts.CollectionChanged -= ContactsOnCollectionChanged;
                        foreach (Interlocutor contact in oldItem.Contacts)
                        {
                            Interlocutors.Remove(contact);
                        }
                    }
                }
            };
        }

        private void ContactsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
        {
            if (args.NewItems != null)
            {
                foreach (Interlocutor newItem in args.NewItems)
                {
                    Interlocutors.Add(newItem);
                }
            }
            if (args.OldItems != null)
            {
                foreach (Interlocutor oldItem in args.OldItems)
                {
                    Interlocutors.Remove(oldItem);
                }
            }
        }

        private void RoomsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
        {
            if (args.NewItems != null)
            {
                foreach (Room newItem in args.NewItems)
                {
                    Rooms.Add(newItem);
                }
            }
            if (args.OldItems != null)
            {
                foreach (Room oldItem in args.OldItems)
                {
                    Rooms.Remove(oldItem);
                }
            }
        }

        public void Dispose()
        {
            var contextSaver = new ModelContextSaver();
            contextSaver.Save(this);
        }
    }
}